﻿namespace Snoop.Infrastructure.Helpers
{
    using System;

    public static class DoubleHelper
    {
        // Const values come from sdk\inc\crt\float.h
#pragma warning disable SA1310 // Field names must not contain underscore
        // ReSharper disable once InconsistentNaming
        private const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
#pragma warning restore SA1310 // Field names must not contain underscore

        /// <summary>
        ///     AreClose - Returns whether or not two doubles are "close".  That is, whether or
        ///     not they are within epsilon of each other.  Note that this epsilon is proportional
        ///     to the numbers themselves to that AreClose survives scalar multiplication.
        ///     There are plenty of ways for this to return false even for numbers which
        ///     are theoretically identical, so no code calling this should fail to work if this
        ///     returns false.  This is important enough to repeat:
        ///     NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
        ///     used for optimizations *only*.
        /// </summary>
        /// <returns>
        ///     bool - the result of the AreClose comparision.
        /// </returns>
        /// <param name="value1"> The first double to compare. </param>
        /// <param name="value2"> The second double to compare. </param>
        public static bool AreClose(this double value1, double value2)
        {
            // in case they are Infinities (then epsilon check does not work)
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            if (value1 == value2)
            {
                return true;
            }

            // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) &lt; DBL_EPSILON
            var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
            var delta = value1 - value2;
            return -eps < delta && eps > delta;
        }

        /// <summary>
        ///     GreaterThan - Returns whether or not the first double is greater than the second double.
        ///     That is, whether or not the first is strictly greater than *and* not within epsilon of
        ///     the other number.  Note that this epsilon is proportional to the numbers themselves
        ///     to that AreClose survives scalar multiplication.  Note,
        ///     There are plenty of ways for this to return false even for numbers which
        ///     are theoretically identical, so no code calling this should fail to work if this
        ///     returns false.  This is important enough to repeat:
        ///     NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
        ///     used for optimizations *only*.
        /// </summary>
        /// <returns>
        ///     bool - the result of the GreaterThan comparision.
        /// </returns>
        /// <param name="value1"> The first double to compare. </param>
        /// <param name="value2"> The second double to compare. </param>
        public static bool GreaterThan(this double value1, double value2)
        {
            return value1 > value2 && !AreClose(value1, value2);
        }
    }
}